/*
 * Copyright (C) 2013 rdrrlabs gmail com,
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package serv

/*
A handler is a 'rooted' URL handler.
For example a handler for "/user" will handle all requests to serve
anything starting with "/user/..." or "/user?..." or "/user". The Handler
can choose to be visible only for GET or POST and be associated with UI or not.
*/

import (
    "appengine"
    "errors"
    "html/template"
    "net/http"
    "strings"
)

//----------------

const (
    // Handler is only visible for POST requests
    HandlerIsPost    = 1 << iota
    // Handler is only visible for DELETE requests
    HandlerIsDelete
    // Handler is only visible for signed users
    HandlerIsSigned
    // Handler is only visible when listing UI tabs
    HandlerIsTab
)

// Type describing a Handler.
type HandlerDesc struct {
    // Mandatory URL root, must start with /, e.g. "/logs".
    URL         string
    // Optional Handler* flag, possibly ORed. 0 for no flags.
    Flags       int
    // Optional group required for accessing this Handler, e.g. "admin"
    Groups      []string
    // Optional. Only needed for UI tabs.
    Title       string
}

func (p *HandlerDesc) Is(flag int) bool {
    return (p.Flags & flag) != 0
}

// Interface for implementing a handler for a given descriptor.
type Handler interface {
    Handler()       *HandlerDesc

    // Create the HTML representing the tab in the main page
    CreateTab     (r *http.Request,
                   c appengine.Context,
                   a *AccessInfo) template.HTML

    // Process UI-less request
    ProcessRequest(w http.ResponseWriter,
                   r *http.Request,
                   c appengine.Context,
                   a *AccessInfo,
                   urlBase string,
                   urlRest string) (httpCode int, err error)

}

//----------------

type Handlers []Handler

var mRegisteredHandlers = make(Handlers, 0)

func init() {
    mRegisteredHandlers = RegisterStatusHandlers(mRegisteredHandlers)
    mRegisteredHandlers = RegisterGCMCrud  (mRegisteredHandlers)
}

// Find all handlers accessible by current user, regardless of the request path.
// However this depends on the request method and whether it's for a UI tab or not.
func FindHandlers(r *http.Request,
                  isUiTab  bool,
                  isSigned bool,
                  a *AccessInfo) (result Handlers) {

    flags := 0
    if isUiTab {
        flags |= HandlerIsTab
    }
    if isSigned {
        flags |= HandlerIsSigned
    }
    if r.Method == "POST" {
        flags |= HandlerIsPost

    } else if r.Method == "DELETE" {
        flags |= HandlerIsDelete
    }

    result = make(Handlers, 0)

    for _, h := range mRegisteredHandlers {
        handler := h.Handler()
        if handler.Flags != flags {
            continue
        }
        if handler.Groups != nil {
            for _, g := range handler.Groups {
                if !a.Is(g) {
                    continue
                }
            }
        }
        result = append(result, h)
    }

    return result
}

// Find first handler that can handle this specific request.
// We don't know whether the handler will be for a tab or not, so this isn't a parameter.
func FindRequestHandler(r *http.Request,
                       isSigned bool,
                       a *AccessInfo) (urlBase string, urlRest string, Handler Handler, err error) {
    u := r.URL.Path
    ulen := len(u)

    flags := 0
    if isSigned {
        flags |= HandlerIsSigned
    }
    if r.Method == "POST" {
        flags |= HandlerIsPost

    } else if r.Method == "DELETE" {
        flags |= HandlerIsDelete
    }

    if ulen > 0 && u[0:1] == "/" {
        for _, h := range mRegisteredHandlers {
            handler := h.Handler()
            hflags  := handler.Flags & ^HandlerIsTab  // Ignore whether handler is for a tab or not
            if hflags != flags {
                continue
            }
            if handler.Groups != nil {
                for _, g := range handler.Groups {
                    if !a.Is(g) {
                        continue
                    }
                }
            }
            urlBase = handler.URL
            blen := len(urlBase)
            if ulen >= blen && u[:blen] == urlBase {
                // get the reminder
                urlRest = u[blen:]
                // reminder must be either empty or start with / or & or ?
                if len(urlRest) == 0 || strings.ContainsAny(string(urlRest[0]), "/&?") {
                    return urlBase, urlRest, h, nil
                }
            }
        }
    }

    return "", "", nil, errors.New("Invalid URL")
}

//----------------
